﻿// <copyright file="CardControlService.cs" company="SharpSTS">
// Copyright (c) 2007, 2008 All Right Reserved, http://sharpsts.com/
//
// This source is subject to the Microsoft Permissive License.
// Please see the License.txt file for more information.
// All other rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// </copyright>
// <author>Barry Dorrans</author>
// <email>barryd@idunno.org</email>
// <date>2008-06-10</date>
// <summary>Contains the implementation for the Card Control web service for issuing and revoking information cards.</summary>

namespace SharpSTS.CardControl
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.ServiceModel;
    using System.ServiceModel.Configuration;
    using System.Xml;

    using Properties;
    using Providers;
    using SharpSTS.Configuration;

    /// <summary>
    /// A web service to implement Information Card issuing and revoking.
    /// </summary>
    [ServiceBehavior(Name = "CardControlService", Namespace = Constants.Namespaces.Service)]
    [IPAddressInspectorBehavior]
    public class CardControlService : ICardControlService
    {
        /// <summary>
        /// Contains the endpoint information for each authentication type the STS supports.
        /// </summary>
        private static readonly Dictionary<AuthenticationType, EndpointInformation> endpoints;

        #region Constructors
        /// <summary>
        /// Creates a new instance of the <see cref="CardControlService"/>.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Design", 
            "CA1065:DoNotRaiseExceptionsInUnexpectedLocations",
            Justification = "The exceptions indicate that the configuration file has errors. In this state the object is not usable, so exceptions may be used."), 
         System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Performance", 
            "CA1810:InitializeReferenceTypeStaticFieldsInline",
            Justification = "endpoints are sourced from the configuration file and cannot be initialised inline.")]
        static CardControlService()
        {
            endpoints = new Dictionary<AuthenticationType, EndpointInformation>();
            Dictionary<string, string> iisEndpoints = new Dictionary<string, string>();
            if (STSSettings.Settings.IISEndpointAddresses != null ||
                STSSettings.Settings.IISEndpointAddresses.Count > 0)
            {
                {
                    foreach (IISEndpoint iisEndpoint in STSSettings.Settings.IISEndpointAddresses)
                    {
                        iisEndpoints.Add(iisEndpoint.Name, iisEndpoint.ServiceFileName);
                    }
                }
            }

            // Parse the application configuration file to extract endpoint information, saving it so we can put it inside the card.
            ServicesSection servicesSection =
                ConfigurationManager.GetSection("system.serviceModel/services") as ServicesSection;

            if (null == servicesSection ||
                null == servicesSection.ElementInformation ||
                !servicesSection.ElementInformation.IsPresent ||
                null == servicesSection.ElementInformation.Properties ||
                servicesSection.ElementInformation.Properties.Count == 0 ||
                null == servicesSection.ElementInformation.Properties[string.Empty])
            {
                throw new ConfigurationErrorsException(Resources.ErrorCannotFindValidServicesConfigurationSection);
            }
            
            // ReSharper disable PossibleNullReferenceException
            ServiceElementCollection servicesCollection =
                servicesSection.ElementInformation.Properties[string.Empty].Value as ServiceElementCollection;            
            //// ReSharper restore PossibleNullReferenceException

            if (null == servicesCollection)
            {
                throw new ConfigurationErrorsException(Resources.ErrorCannotFindValidServicesServiceElementConfigurationSection);
            }

            foreach (ServiceElement serviceElement in servicesCollection)
            {
                AuthenticationType? endpointType = null;

                switch (serviceElement.Name)
                {
                    case "SharpSTS.UserNameAuthSTS":
                        endpointType = AuthenticationType.UserNamePassword;
                        break;

                    case "SharpSTS.SelfIssuedSamlAuthSTS":
                        endpointType = AuthenticationType.SelfIssued;
                        break;

                    case "SharpSTS.CertificateAuthSTS":
                        endpointType = AuthenticationType.Certificate;
                        break;
                }

                if (null == endpointType)
                {
                    continue;
                }

                UriBuilder endpointBuilder = new UriBuilder(STSSettings.Settings.BaseSTSAddress);
                UriBuilder mexEndpointBuilder = new UriBuilder(STSSettings.Settings.BaseMetadataExchangeAddress);
                if (iisEndpoints.Count == 0)
                {
                    foreach (ServiceEndpointElement endpoint in serviceElement.Endpoints)
                    {
                        switch (endpoint.Contract)
                        {
                            case "SharpSTS.IWSTrustContract":
                                endpointBuilder.Path += endpoint.Address;
                                break;
                            case "IMetadataExchange":
                                mexEndpointBuilder.Path += endpoint.Address;
                                break;
                        }
                    }
                }
                else
                {
                    endpointBuilder.Path += iisEndpoints[serviceElement.Name];
                    mexEndpointBuilder.Path += iisEndpoints[serviceElement.Name] + "/mex";                        
                }

                endpoints.Add((AuthenticationType) endpointType, new EndpointInformation(endpointBuilder.Uri, mexEndpointBuilder.Uri));
            }
        }
        #endregion

        #region ICardControlService Members
        /// <summary>
        /// Gets an information card.
        /// </summary>
        /// <param name="request">The request parameters.</param>
        /// <returns>
        /// A <see cref="GetResponseMessage"/> containing the requested information card.
        /// </returns>
        public GetResponseMessage GetCard(GetRequestMessage request)
        {
            ManagedInformationCard managedCard;

            if (null != request.Body.Claims && request.Body.Claims.GetUpperBound(0) != 0)
            {
                List<ManagedCardClaim> claims = new List<ManagedCardClaim>();
                claims.AddRange(request.Body.Claims);
                managedCard = new ManagedInformationCard(request.Body.AuthenticationType, claims.AsReadOnly());
            }
            else
            {
                managedCard = new ManagedInformationCard(request.Body.AuthenticationType);
            }

            managedCard.ServiceAddress = endpoints[request.Body.AuthenticationType].EndpointAddress;
            managedCard.MexAddress = endpoints[request.Body.AuthenticationType].MexAddress;
            managedCard.Name = request.Body.CardName;
            
            managedCard.Id = AuthorisationPolicyManager.GetCardIdForUserId(request.Body.UserIdentifier, request.Body.AuthenticationType);
            
            managedCard.CredentialHint = request.Body.AuthenticationHint.Hint;
            managedCard.CredentialDescription = request.Body.AuthenticationHint.Description;

            if (request.Body.ExpiryDateTime != DateTime.MinValue)
            {
                managedCard.ExpiryDateTime = request.Body.ExpiryDateTime;
            }

            if (!string.IsNullOrEmpty(request.Body.ImageFileName))
            {
                managedCard.ImageFileName = request.Body.ImageFileName;
            }

            XmlElement signedCard = managedCard.GetSignedXmlForCard();

            GetResponseMessage response = new GetResponseMessage
                                              {
                                                  Body = new GetResponseBody
                                                             {
                                                                 Card = signedCard
                                                             }
                                              };

            return response;
        }

        /// <summary>
        /// Revokes an information card.
        /// </summary>
        /// <param name="request">The request parameters.</param>
        public void RevokeCard(RevokeCardRequestMessage request)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Revokes all cards for a particular users.
        /// </summary>
        /// <param name="request">The request parameters.</param>
        public void RevokeAllCardsForUser(RevokeCardRequestMessage request)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Reissues a particular information card.
        /// </summary>
        /// <param name="request">The request parameters.</param>
        /// <returns>
        /// A <see cref="GetResponseMessage"/> containing the requested information card.
        /// </returns>
        public GetResponseMessage Reissue(ReissueRequestMessage request)
        {
            throw new NotImplementedException();
        }
        #endregion

        #region Endpoint Information container
        /// <summary>
        /// Respresents the endpoint information for a listening endpoint with an associated mex listener.
        /// </summary>
        private class EndpointInformation
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="EndpointInformation"/> class.
            /// </summary>
            /// <param name="address">The endpoint address.</param>
            /// <param name="mexAddress">The endpoint mex address.</param>
            public EndpointInformation(Uri address, Uri mexAddress)
            {
                this.EndpointAddress = address;
                this.MexAddress = mexAddress;
            }

            /// <summary>
            /// Gets the endpoint address.
            /// </summary>
            /// <value>The endpoint address.</value>
            public Uri EndpointAddress
            {
                get;
                private set;
            }

            /// <summary>
            /// Gets the endpoint mex address.
            /// </summary>
            /// <value>The endpoint mex address.</value>
            public Uri MexAddress
            {
                get;
                private set;
            }
        }
        #endregion
    }
}
